What is birpc?
The 'birpc' npm package is a lightweight library for creating bidirectional RPC (Remote Procedure Call) communication between different contexts, such as between a web worker and the main thread, or between an iframe and its parent window. It simplifies the process of sending and receiving messages, handling responses, and managing errors.
What are birpc's main functionalities?
Basic RPC Communication
This feature allows you to set up basic RPC communication between two contexts. In this example, the main thread defines an 'add' function that can be called from the worker.
const { createBirpc } = require('birpc');
// In the main thread
const rpc = createBirpc({
add: (a, b) => a + b
}, {
post: (msg) => worker.postMessage(msg),
on: (fn) => worker.onmessage = (e) => fn(e.data)
});
// In the worker
const rpc = createBirpc({}, {
post: (msg) => postMessage(msg),
on: (fn) => onmessage = (e) => fn(e.data)
});
// Call the 'add' function from the worker
rpc.add(1, 2).then(result => console.log(result)); // 3
Error Handling
This feature demonstrates how to handle errors in RPC communication. The main thread defines a function that throws an error, and the worker catches and logs the error message.
const { createBirpc } = require('birpc');
// In the main thread
const rpc = createBirpc({
throwError: () => { throw new Error('Something went wrong'); }
}, {
post: (msg) => worker.postMessage(msg),
on: (fn) => worker.onmessage = (e) => fn(e.data)
});
// In the worker
const rpc = createBirpc({}, {
post: (msg) => postMessage(msg),
on: (fn) => onmessage = (e) => fn(e.data)
});
// Call the 'throwError' function from the worker
rpc.throwError().catch(err => console.error(err.message)); // 'Something went wrong'
Bidirectional Communication
This feature shows how to set up bidirectional communication, where both the main thread and the worker can define and call functions on each other.
const { createBirpc } = require('birpc');
// In the main thread
const rpc = createBirpc({
mainFunction: () => 'Hello from main thread'
}, {
post: (msg) => worker.postMessage(msg),
on: (fn) => worker.onmessage = (e) => fn(e.data)
});
// In the worker
const rpc = createBirpc({
workerFunction: () => 'Hello from worker'
}, {
post: (msg) => postMessage(msg),
on: (fn) => onmessage = (e) => fn(e.data)
});
// Call the 'workerFunction' from the main thread
rpc.workerFunction().then(result => console.log(result)); // 'Hello from worker'
// Call the 'mainFunction' from the worker
rpc.mainFunction().then(result => console.log(result)); // 'Hello from main thread'
Other packages similar to birpc
comlink
Comlink is a library that makes WebWorkers enjoyable. It allows you to use WebWorkers as if they were local objects. Compared to birpc, Comlink provides a more seamless and higher-level abstraction for working with WebWorkers, but it is primarily focused on WebWorkers and may not be as flexible for other contexts.
postmate
Postmate is a powerful, simple, promise-based library for cross-domain iframe communication. It allows you to easily communicate between an iframe and its parent window. While birpc can also handle iframe communication, Postmate is specifically designed for this purpose and provides a more specialized API.
rpc-websockets
rpc-websockets is a library for creating RPC servers and clients over WebSockets. It is more suitable for networked applications where WebSocket communication is required. Compared to birpc, rpc-websockets is more focused on network communication rather than inter-context communication within the same application.
birpc
Message-based two-way remote procedure call. Useful for WebSockets and Workers communication.
Features
- Intuitive - call remote functions just like locals, with Promise to get the response
- TypeScript - safe function calls for arguments and returns
- Protocol agonostic - WebSocket, MessageChannel, any protocols with messages communication would work!
- Zero deps, ~0.5KB
Examples
Using WebSocket
When using WebSocket, you need to pass your custom serializer and deserializer.
Client
import type { ServerFunctions } from './types'
const ws = new WebSocket('ws://url')
const clientFunctions: ClientFunctions = {
hey(name: string) {
return `Hey ${name} from client`
}
}
const rpc = createBirpc<ServerFunctions>(
clientFunctions,
{
post: data => ws.send(data),
on: data => ws.on('message', data),
serialize: v => JSON.stringify(v),
deserialize: v => JSON.parse(v),
},
)
await rpc.hi('Client')
Server
import type { ClientFunctions } from './types'
import { WebSocketServer } from 'ws'
const serverFunctions: ServerFunctions = {
hi(name: string) {
return `Hi ${name} from server`
}
}
const wss = new WebSocketServer()
wss.on('connection', (ws) => {
const rpc = createBirpc<ClientFunctions>(
serverFunctions,
{
post: data => ws.send(data),
on: fn => ws.on('message', fn),
serialize: v => JSON.stringify(v),
deserialize: v => JSON.parse(v),
},
)
await rpc.hey('Server')
})
Circular References
As JSON.stringify
does not supporting circular references, we recommend using flatted
as the serializer when you expect to have circular references.
import { parse, stringify } from 'flatted'
const rpc = createBirpc<ServerFunctions>(
functions,
{
post: data => ws.send(data),
on: fn => ws.on('message', fn),
serialize: v => stringify(v),
deserialize: v => parse(v),
},
)
Using MessageChannel
MessageChannel will automatically serialize the message and support circular references out-of-box.
export const channel = new MessageChannel()
Bob
import type { AliceFunctions } from './types'
import { channel } from './channel'
const Bob: BobFunctions = {
hey(name: string) {
return `Hey ${name}, I am Bob`
}
}
const rpc = createBirpc<AliceFunctions>(
Bob,
{
post: data => channel.port1.postMessage(data),
on: fn => channel.port1.on('message', fn),
},
)
await rpc.hi('Alice')
Alice
import type { BobFunctions } from './types'
import { channel } from './channel'
const Alice: AliceFunctions = {
hi(name: string) {
return `Hi ${name}, I am Alice`
}
}
const rpc = createBirpc<BobFunctions>(
Alice,
{
post: data => channel.port2.postMessage(data),
on: fn => channel.port2.on('message', fn),
},
)
await rpc.hey('Alice')
One-to-multiple Communication
Refer to ./test/group.test.ts as an example.
License
MIT License © 2021 Anthony Fu